/**
 * @(#)Logic.java
 *
 * Copyright: Copyright (c) 2003 Carnegie Mellon University
 *
 * This program is the implementation of the original system in the Assignment 2 of "Architectures
 * of Software Systems" class. For the details of each classes, please refer to the provided javadoc
 * documents. You will also find some useful information such as implementation decisions, coding
 * standards, and naming conventions in the README file.
 */


import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;


/**
 * This class represents a logic node which is responsible for providing high-level data access
 * methods to clients using RMI mechanism. It depends on the functionalities of data node
 * which is made available also using RMI mechanism, thus acting as a "bridge" between client nodes
 * and the data node.
 *
 * @author Jung Soo Kim
 * @version 1.0
 */
public class Logic extends UnicastRemoteObject implements RILogic {

    private static final String LOGIC_NAME = "Logic";
    private static final String DATA_NAME  = "Data";
    private static final String LOG_NAME = "Log";
    private HashMap<Integer, String> sessionMap ; 

    /**
     * Remote reference to data node.
     */
    protected RIData rmiDataNode;
    protected RILog rmiLogNode;

    /**
     * Constructs a logic node. A remote reference to the data node is acquired at the time of
     * creation.
     *
     * @param sDataName the name of data node
     */
    public Logic(String sDataName, String sLogName)
           throws RemoteException, NotBoundException, MalformedURLException {
        // Get the remote reference to data node.
    	
        this.rmiDataNode = (RIData) Naming.lookup(sDataName);
        this.rmiLogNode = (RILog) Naming.lookup(sLogName);
          
        sessionMap = new HashMap<Integer, String>() ; 
    }

    /**
     * List all students.
     *
     * @return a string result of command processing
     */
    public String getAllStudents(int sessionID)
                  throws RemoteException {
    	if ( !checkSessionID(sessionID) ) {
    		return ">> Failed : Session is invalid"; 
    	}
    	
        // Get all student records.
        ArrayList vStudent = this.rmiDataNode.getAllStudentRecords();

        // Construct a list of student information and return it.
        String sReturn = "";
        for (int i=0; i<vStudent.size(); i++) {
            sReturn += (i == 0 ? "" : "\n") + ((Student) vStudent.get(i)).toString();
        }
        WriteLog(getUserID(sessionID), "getAllStudents");
        return sReturn;
    }
    
    
    @Override
	public String login(String id, String password) throws RemoteException {
    	
    	int sessionID = 0 ; 
    	
    	if ( id.equals(id) && password.equals( rmiDataNode.getPassword(id)) )
    	{    		
    		sessionID = (int) ( Math.random() * 50000.0 ) ;   		    
    		sessionMap.put(sessionID, id);
    		
    		WriteLog(getUserID(sessionID), "LogIn suceess");
    	}
    	else
    	{
    		WriteLog(id, "LogIn failed");
    	}
    	
    	
    	return Integer.valueOf(sessionID).toString();
    }

    public void WriteLog(String sID, String sCommand)
    {
    	try
    	{
	    	Calendar cal = Calendar.getInstance();
	        SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS");
	        df.format(cal.getTime());
	        
	        this.rmiLogNode.writeLog(sID, sCommand, df.format(cal.getTime()));
    	}
    	catch (RemoteException e)
    	{
    		e.printStackTrace();
    	}
    }
    /**
     * List all courses.
     *
     * @return a string result of command processing
     */
    public String getAllCourses(int sessionID)
                  throws RemoteException {
    	if ( !checkSessionID(sessionID) ) {
    		return ">> Failed : Session is invalid"; 
    	}
        // Get all course records.
        ArrayList vCourse = this.rmiDataNode.getAllCourseRecords();

        // Construct a list of course information and return it.
        String sReturn = "";
        for (int i=0; i<vCourse.size(); i++) {
            sReturn += (i == 0 ? "" : "\n") + ((Course) vCourse.get(i)).toString();
        }
        
        WriteLog(getUserID(sessionID), "getAllCourses");
        
        return sReturn;
    }

    /**
     * List students who registered for a course.
     *
     * @param sCID a string representing course ID
     * @param sSection a string representing course section
     * @return a string result of command processing
     */
    public String getRegisteredStudents(int sessionID, String sCID, String sSection)
                  throws RemoteException {
    	if ( !checkSessionID(sessionID) ) {
    		return ">> Failed : Session is invalid"; 
    	}
        // Get the list of students who registered for the given course.
        Course objCourse = this.rmiDataNode.getCourseRecord(sCID, sSection);
        if (objCourse == null) {
            return "Invalid course ID or course section";
        }
        ArrayList vStudent = objCourse.getRegisteredStudents();

        // Construct a list of student information and return it.
        String sReturn = "";
        for (int i=0; i<vStudent.size(); i++) {
            sReturn += (i == 0 ? "" : "\n") + ((Student) vStudent.get(i)).toString();
        }
        
        WriteLog(getUserID(sessionID), "getRegisteredStudents");
        
        return sReturn;
    }

    /**
     * List courses a student has registered for.
     *
     * @param sSID a string representing student ID
     * @return a string result of command processing
     */
    public String getRegisteredCourses(int sessionID, String sSID)
                  throws RemoteException {
    	if ( !checkSessionID(sessionID) ) {
    		return ">> Failed : Session is invalid"; 
    	}
        // Get the list of courses the given student has registered for.
        Student objStudent = this.rmiDataNode.getStudentRecord(sSID);
        if (objStudent == null) {
            return "Invalid student ID";
        }
        ArrayList vCourse = objStudent.getRegisteredCourses();

        // Construct a list of course information and return it.
        String sReturn = "";
        for (int i=0; i<vCourse.size(); i++) {
            sReturn += (i == 0 ? "" : "\n") + ((Course) vCourse.get(i)).toString();
        }
        
        WriteLog(getUserID(sessionID), "getRegisteredCourses");
        
        return sReturn;
    }

    /**
     * List courses a student has completed.
     *
     * @param sSID a string representing student ID
     * @return a string result of command processing
     */
    public String getCompletedCourses(int sessionID, String sSID)
                  throws RemoteException {
    	if ( !checkSessionID(sessionID) ) {
    		return ">> Failed : Session is invalid"; 
    	}
        // Get the list of courses the given student has completed.
        Student objStudent = this.rmiDataNode.getStudentRecord(sSID);
        if (objStudent == null) {
            return "Invalid student ID";
        }
        ArrayList vCourseID = objStudent.getCompletedCourses();

        // Construct a list of course information and return it.
        String sReturn = "";
        for (int i=0; i<vCourseID.size(); i++) {
            String sCID = (String) vCourseID.get(i);
            String sName = this.rmiDataNode.getCourseName(sCID);
            sReturn += (i == 0 ? "" : "\n") + sCID + " " + (sName == null ? "Unknown" : sName);
        }
        
        WriteLog(getUserID(sessionID), "getCompletedCourses");
        
        return sReturn;
    }

    /**
     * Register a student for a course. Conflict check is done before making a registration.
     *
     * @param sSID a string representing student ID
     * @param sCID a string representing course ID
     * @param sSection a string representing course section
     * @return a string result of command processing
     */
    public String makeARegistration(int sessionID, String sSID, String sCID, String sSection)
                  throws RemoteException {
    	if ( !checkSessionID(sessionID) ) {
    		return ">> Failed : Session is invalid"; 
    	}
        // Get the student and course records.
        Student objStudent = this.rmiDataNode.getStudentRecord(sSID);
        Course objCourse = this.rmiDataNode.getCourseRecord(sCID, sSection);
        if (objStudent == null) {
            return "Invalid student ID";
        }
        if (objCourse == null) {
            return "Invalid course ID or course section";
        }

        // Check if the given course conflicts with any of the courses the student has registered.
        ArrayList vCourse = objStudent.getRegisteredCourses();
        for (int i=0; i<vCourse.size(); i++) {
            if (((Course) vCourse.get(i)).conflicts(objCourse)) {
                return "Registration cconflicts";
            }
        }

        // Request validated. Proceed to register.
        this.rmiDataNode.makeARegistration(sSID, sCID, sSection);
        
        WriteLog(getUserID(sessionID), "makeARegistration");
        
        return "Successful!";
    }

    boolean checkSessionID(int sessionID) 
    {
    	if ( sessionMap.containsKey(sessionID))
    	{
    		return true ;
    	}
    	
    	WriteLog(getUserID(sessionID), "Session not exist (Invalid Session).");
    	
    	return false ; 
    }
    
    String getUserID(int sessionID) {
    	return sessionMap.get(sessionID);
    }
    
    /**
     * Creates a logic node and starts it. Two parameters are expected, the first one as data node
     * name and the second one as logic node name.
     *
     * @param args array of input parameters
     */
    public static void main(String args[]) {
        // Check the number of parameters.
        if (args.length != 0) {
            System.out.println("Incorrect number of parameters");
            System.out.println("Usage: java Logic");
            System.exit(1);
        }

        try {
            // Create a logic node and make it available through RMI.
            RILogic objLogic = new Logic(DATA_NAME, LOG_NAME);
            Naming.rebind(LOGIC_NAME, objLogic);
            System.out.println("Logic node is ready to serve.");

            // Wait for user interrupt.
            System.out.println("Press Enter to terminate.");
            System.in.read();

            // Clean up and finish.
            Naming.unbind(LOGIC_NAME);
            System.out.println("Logic node is leaving, bye. Press ctrl-c if the delay is too long.");
        }
        catch (java.rmi.ConnectException e) {
            // Show error message and quit.
            System.err.println("Java RMI error: check if rmiregistry is running.");
            System.exit(1);
	}
        catch (java.rmi.NotBoundException e) {
            // Show error message and quit.
            System.err.println("Java RMI error: check if data node is running.");
            System.exit(1);
        }
        catch (Exception e) {
            // Dump the exception information for debugging.
            System.out.println("Unexpected exception at " + LOGIC_NAME);
            e.printStackTrace();
            System.exit(1);
        }
    }
}
